#include "common.h"
#include "adc.h"

int adc_init(int bit, int speed, int st, int hwavg, int dma_en){
    if(bit<0x00||bit>0x03
     ||speed<0||speed>1
     ||st<0x00||st>0x04
     ||hwavg<0x00||hwavg>0x04)
        return -1;
    /* Enable clock gate */
    SIM_SCGC6|=SIM_SCGC6_ADC0_MASK;

    if(adc_cal()==-1){
        printf("\nADC calibration failed...\n\n");
        return -2;
    }

    /* Clear ADC0 registers */
    ADC0_CFG1=0;
    ADC0_CFG2=0;
    ADC0_SC1A=ADC_SC1_ADCH_MASK;
    ADC0_SC2=0;
    ADC0_SC3=0;

    /* Select ADC resolution mode */
    ADC0_CFG1|=ADC_CFG1_MODE(bit);

    /* Mantain asynchronous clock and clock output */
    ADC0_CFG2|=ADC_CFG2_ADACKEN_MASK;

    /* High speed configuration */
    if(speed==HIGH_SPEED)
        ADC0_CFG2|=ADC_CFG2_ADHSC_MASK;

    /* Long sample time configuration */
    if(st<ADCK_NO){
        ADC0_CFG1|=ADC_CFG1_ADLSMP_MASK; // Enable long sample time
        ADC0_CFG2|=ADC_CFG2_ADLSTS(st); // select sample time
    }

    /* Hardware average configuration */
    if(hwavg<AVGS_NO)
        ADC0_SC3|=ADC_SC3_AVGE_MASK|ADC_SC3_AVGS(hwavg); // Enable hardware average

    if(dma_en)
        ADC0_SC2=ADC_SC2_DMAEN_MASK; // Enable ADC DMA request when conversion complete

    return 0;
}

int adc_cal(void){
    ADC_MemMapPtr adcmap=ADC0_BASE_PTR;
    unsigned short cal_var;

    ADC_SC2_REG(adcmap)&=~ADC_SC2_ADTRG_MASK ; // Enable Software Conversion Trigger for Calibration Process    - ADC0_SC2 = ADC0_SC2 | ADC_SC2_ADTRGW(0);   
    ADC_SC3_REG(adcmap)&=(~ADC_SC3_ADCO_MASK&~ADC_SC3_AVGS_MASK); // set single conversion, clear avgs bitfield for next writing
    ADC_SC3_REG(adcmap)|=(ADC_SC3_AVGE_MASK|ADC_SC3_AVGS(AVGS_32));  // Turn averaging ON and set at max value ( 32 )


    ADC_SC3_REG(adcmap)|=ADC_SC3_CAL_MASK;      // Start CAL
    while((ADC_SC1_REG(adcmap, ADC_A)&ADC_SC1_COCO_MASK)==0x00); // Wait calibration end

    if((ADC_SC3_REG(adcmap)&ADC_SC3_CALF_MASK)){
        return -1;    // Check for Calibration fail error and return 
    }
    // Calculate plus-side calibration
    cal_var=0x00;
  
    cal_var=ADC_CLP0_REG(adcmap); 
    cal_var+=ADC_CLP1_REG(adcmap);
    cal_var+=ADC_CLP2_REG(adcmap);
    cal_var+=ADC_CLP3_REG(adcmap);
    cal_var+=ADC_CLP4_REG(adcmap);
    cal_var+=ADC_CLPS_REG(adcmap);

    cal_var=cal_var/2;
    cal_var|=0x8000; // Set MSB

    ADC_PG_REG(adcmap)=ADC_PG_PG(cal_var);
 

    // Calculate minus-side calibration
    cal_var=0x00;

    cal_var=ADC_CLM0_REG(adcmap); 
    cal_var+=ADC_CLM1_REG(adcmap);
    cal_var+=ADC_CLM2_REG(adcmap);
    cal_var+=ADC_CLM3_REG(adcmap);
    cal_var+=ADC_CLM4_REG(adcmap);
    cal_var+=ADC_CLMS_REG(adcmap);

    cal_var=cal_var/2;

    cal_var|=0x8000; // Set MSB

    ADC_MG_REG(adcmap)=ADC_MG_MG(cal_var); 
  
    ADC_SC3_REG(adcmap)&=~ADC_SC3_CAL_MASK ; /* Clear CAL bit */

    return 0;
}

uint16 adc_read(int ch){
    ADC0_SC1A&=~ADC_SC1_ADCH_MASK; // clear ADCH value

    ADC0_SC1A|=ADC_SC1_ADCH(ch); // specify input channel

    while((ADC0_SC1A&ADC_SC1_COCO_MASK)==0x0);

    return ADC0_RA;
}
